package com.fc.test.compiler.baseutil.smc.compiler;

import com.fc.test.compiler.baseutil.smc.model.CompilerModel;
import com.google.gson.annotations.SerializedName;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * In-memory compile Java source code as String.
 *
 * @author michael
 */
public class JavaStringCompiler {
    private MemoryClassLoader memoryClassLoader;
    private JavaCompiler compiler;
    private StandardJavaFileManager stdManager;
    private static final Logger logger = LoggerFactory.getLogger(JavaStringCompiler.class);

    public JavaStringCompiler() {
        this.compiler = ToolProvider.getSystemJavaCompiler();
        DiagnosticCollector<JavaFileObject> diagnostics =
                new DiagnosticCollector<JavaFileObject>();
        this.stdManager = compiler.getStandardFileManager(null, null, null);
        memoryClassLoader = new MemoryClassLoader(Thread.currentThread().getContextClassLoader());
    }

    public JavaStringCompiler(ClassLoader classLoader) {
        this.compiler = ToolProvider.getSystemJavaCompiler();
        this.stdManager = compiler.getStandardFileManager(null, null, null);
        memoryClassLoader = new MemoryClassLoader(classLoader == null ? Thread.currentThread().getContextClassLoader() : classLoader);
    }

    /**
     * Compile a Java source file in memory.
     *
     * @param fileName Java file name, e.g. "Test.java"
     * @param source   The source code as String.
     * @return The compiled results as Map that contains class name as key,
     * class binary as value.
     * @throws IOException If compile error.
     */
    public Map<String, byte[]> compile(String fileName, String source) throws IOException {
        MemoryJavaFileManager manager = new MemoryJavaFileManager(stdManager);
        try {
            JavaFileObject javaFileObject = manager.makeStringSource(fileName, source);

            List<String> options = new ArrayList<>();
            options.add("-classpath");

            String classPaths = System.getProperty("java.class.path");
            StringBuilder sb = new StringBuilder(classPaths);
            sb.append(File.pathSeparator);
            String path = SerializedName.class.getResource("").getPath();

            String libPath = StringUtils.substringBetween(path, "file:", "!");
            String libaryPath = StringUtils.substringBeforeLast(libPath, "/");
            File file1 = new File(libaryPath + "/");
            if (file1.exists() && file1.isDirectory()) {
                File[] jarFiles = file1.listFiles(pathname -> pathname.getName().endsWith(".jar"));
                if (jarFiles != null) {
                    sb.append(Stream.of(jarFiles).map(File::getPath).collect(Collectors.joining(File.pathSeparator)))
                            .append(File.pathSeparator);
                }
            }
            sb.append(StringUtils.substringBeforeLast(libaryPath, "/") + "/classes");
            logger.debug("CLASSPATH:" + sb.toString());
            options.add(sb.toString());

            CompilationTask task = compiler.getTask(null, manager, null, options, null, Arrays.asList(javaFileObject));
            Boolean result = task.call();
            if (result == null || !result.booleanValue()) {
                logger.error("编译源代码出错，出错的源代码:\r\n{}\r\n", source);
                throw new RuntimeException("Compilation failed.");
            }
            return manager.getClassBytes();
        } finally {
            manager.close();
        }
    }

    public Class<?> compile(CompilerModel modle, ClassLoader classLoader) throws IOException, ClassNotFoundException {
        MemoryJavaFileManager manager = new MemoryJavaFileManager(stdManager);
        try {
            JavaFileObject javaFileObject = manager.makeStringSource(modle.fileName(), modle.toString());
            CompilationTask task = compiler.getTask(null, manager, null, null, null, Arrays.asList(javaFileObject));
            Boolean result = task.call();
            if (result == null || !result.booleanValue()) {
                logger.error("编译源代码出错，出错的源代码:\r\n{}\r\n", modle.toString());
                throw new RuntimeException("Compilation failed.");
            }
            MemoryClassLoader memoryClassLoader = new MemoryClassLoader(classLoader == null ? Thread.currentThread().getContextClassLoader() : classLoader);
            memoryClassLoader.addClassBytes(manager.getClassBytes());
            return memoryClassLoader.loadClass("com.jfireframe.smc.output." + modle.className());
        } finally {
            manager.close();
        }
    }

    public Class<?> compile(CompilerModel modle) throws IOException, ClassNotFoundException {
        MemoryJavaFileManager manager = new MemoryJavaFileManager(stdManager);
        try {
            JavaFileObject javaFileObject = manager.makeStringSource(modle.fileName(), modle.toString());
            CompilationTask task = compiler.getTask(null, manager, null, null, null, Arrays.asList(javaFileObject));
            Boolean result = task.call();
            if (result == null || !result.booleanValue()) {
                logger.error("编译源代码出错，出错的源代码:\r\n{}\r\n", modle.toString());
                throw new RuntimeException("Compilation failed.");
            }
            memoryClassLoader.addClassBytes(manager.getClassBytes());
            return memoryClassLoader.loadClass("com.jfireframe.smc.output." + modle.className());
        } finally {
            manager.close();
        }
    }

}
